package com.ygqh.baby.utils;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Random;

/**
 * @Author: sunshuo
 * @Date: 2019/3/26 18:31
 * @Version: 1.0
 */
public class MsgCrypt {

	/**
	 * 编码格式
	 */
	private final Charset charset;

	/**
	 * 加密秘钥
	 */
	private final byte[] aesKey;

	/**
	 * 偏移量
	 */
	private final byte[] ivByte;

	/**
	 * 当前账户app_id
	 */
	private final String appId;

	public MsgCrypt(String appId, String aesKey) {
		System.out.println("aesKey="+aesKey);
		this.appId = appId;
		this.aesKey = Base64Util.decode(aesKey + "=");
		this.ivByte = Arrays.copyOfRange(this.aesKey, 0, 16);
		this.charset = Charset.forName("utf-8");
	}

	public MsgCrypt(String appId, String aesKey, String iv) {
		this(appId, aesKey, iv, "utf-8");
	}

	public MsgCrypt(String appId, String aesKey, String iv, String charsetName) {
		this.appId = appId;
		this.aesKey = Base64Util.decode(aesKey);
		this.ivByte = Base64Util.decode(iv);
		this.charset = Charset.forName(charsetName);
	}

	/**
	 * 对密文进行解密.
	 *
	 * @param encrypt 需要解密的密文
	 * @return 解密得到的明文
	 * @throws AesException
	 */
	public String decryptMsg(String encrypt) throws AesException {
		byte[] original;
		try {
			// 设置解密模式为AES的CBC模式
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
			IvParameterSpec ivParameterSpec = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
			cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

			// 使用BASE64对密文进行解码
			byte[] data = Base64Util.decode(encrypt);
			assert data != null;
			original = cipher.doFinal(data);
		} catch (Exception e) {
			e.printStackTrace();
			throw new AesException(AesException.DECRYPTAES_ERROR);
		}

		try {
			// 去除补位字符
			byte[] bytes = PKCS7Encoder.decode(original);
			return new String(Arrays.copyOfRange(bytes, 20, bytes.length), charset);
		} catch (Exception e) {
			e.printStackTrace();
			throw new AesException(AesException.ILLEGAL_BUFFER);
		}
	}

	/**
	 * 对明文进行加密.
	 *
	 * @param text      需要加密的明文
	 * @return
	 * @throws AesException aes加密失败
	 */
	public String encrypt(String text) throws AesException {
		String randomStr = getRandomStr();
		ByteGroup byteCollector = new ByteGroup();
		byte[] randomStrBytes = randomStr.getBytes(charset);
		byte[] textBytes = text.getBytes(charset);
		byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
		byte[] appidBytes = appId.getBytes(charset);

		// randomStr + networkBytesOrder + text + appid
		byteCollector.addBytes(randomStrBytes)
				.addBytes(networkBytesOrder)
				.addBytes(textBytes)
				.addBytes(appidBytes);

		// ... + pad: 使用自定义的填充方式对明文进行补位填充
		byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
		byteCollector.addBytes(padBytes);

		// 获得最终的字节流, 未加密
		byte[] unencrypted = byteCollector.toBytes();

		try {
			// 设置加密模式为AES的CBC模式
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
			IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
			cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);

			// 加密
			byte[] encrypted = cipher.doFinal(unencrypted);

			// 使用BASE64对加密后的字符串进行编码
			return Base64Util.encode(encrypted);
		} catch (Exception e) {
			e.printStackTrace();
			throw new AesException(AesException.ENCRYPT_AES_ERROR);
		}
	}

	/**
	 * 生成4个字节的网络字节序
	 *
	 * @param sourceNumber 网络字
	 * @return
	 */
	private byte[] getNetworkBytesOrder(int sourceNumber) {
		byte[] orderBytes = new byte[4];
		orderBytes[3] = (byte) (sourceNumber & 0xFF);
		orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
		orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
		orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
		return orderBytes;
	}

	/**
	 * 随机生成16位字符串
	 *
	 * @return
	 */
	private String getRandomStr() {
		String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
		Random random = new Random();
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < 16; i++) {
			int number = random.nextInt(base.length());
			builder.append(base.charAt(number));
		}
		return builder.toString();
	}
}
